package repository

import (
	"context"
	"database/sql"
	"errors"
	"log"
	"time"
	"webook/internal/domain"
	"webook/internal/repository/cache"
	"webook/internal/repository/dao"
)

var (
	ErrDuplicateUser            = dao.ErrDuplicateEmail
	ErrUserNotFound             = dao.ErrRecordNotFound //这里不能命名为ErrRecordNotFound 因为repository是跟业务强相关的 必须明确表示是哪个业务出现了问题
	ErrUserNameOrProfileTooLong = dao.ErrDataTooLong
)

type UserRepository interface {
	Create(ctx context.Context, u domain.User) error
	FindByEmail(ctx context.Context, email string) (domain.User, error)
	Update(ctx context.Context, u domain.User) error
	FindById(ctx context.Context, userId int64) (domain.User, error)
	FindByPhone(ctx context.Context, phone string) (domain.User, error)
	FindByWechat(ctx context.Context, openId string) (domain.User, error)
}

type CacheUserRepository struct {
	dao   dao.UserDao
	cache cache.UserCache
}

func NewCacheUserRepository(d dao.UserDao, c cache.UserCache) UserRepository {
	return &CacheUserRepository{dao: d, cache: c}
}

// Create 这里的注册功能叫做创建 跟业务代码做区分
func (repo *CacheUserRepository) Create(ctx context.Context, u domain.User) error {
	return repo.dao.Insert(ctx, repo.toEntity(u))
}

func (repo *CacheUserRepository) FindByEmail(ctx context.Context, email string) (domain.User, error) {
	u, err := repo.dao.FindByEmail(ctx, email)
	if err != nil {
		return domain.User{}, err
	}
	return repo.toDomain(u), nil
}

func (repo *CacheUserRepository) Update(ctx context.Context, u domain.User) error {
	return repo.dao.Update(ctx, dao.User{
		Id:          u.Id,
		Birthday:    u.Birthday.UnixMilli(),
		UserName:    u.UserName,
		UserProfile: u.UserProfile,
	})
}
func (repo *CacheUserRepository) FindById(ctx context.Context, userId int64) (domain.User, error) {
	du, err := repo.cache.Get(ctx, userId)
	//为了防止影响数据库 一种接近降级的保守写法 redis崩溃后 业务不可用 但是保住了数据库
	//switch {
	//case err == nil:
	//	return du, nil
	//case errors.Is(err, self_cache.ErrKeyNotExist):
	//	//查数据库
	//default:
	//	return domain.User{}, err
	//}
	if err == nil { //只要error为nil 就返回；error不为nil，查询数据库
		return du, nil
	}
	//检测限流/熔断/降级 标记位
	if ctx.Value("downgrade") == true {
		return du, errors.New("触发降级 不再查询数据库")
	}

	/*
		error有两种可能
		1. key不存在 说明redis是正常的
		2. 访问redis有问题 网络问题或是redis本身崩溃 导致数据库负载提高
	*/
	u, err := repo.dao.FindById(ctx, userId)
	if err != nil {
		return domain.User{}, err
	}
	du = repo.toDomain(u)
	err = repo.cache.Set(ctx, du)
	if err != nil { //2.set缓存失败，下次查询还是直接查询数据库，“缓存击穿” 推荐这种 因为redis崩溃的可能性较少
		log.Default().Printf("FindById set self_cache error:%v", err)
	}
	//异步写法 比较推荐 进一步提高查询性能
	//go func() {
	//	err = repo.self_cache.Set(ctx, du)
	//	if err != nil {
	//		log.Default().Printf("FindById set self_cache error:%v", err)
	//	}
	//}()
	return du, nil
}
func (repo *CacheUserRepository) toDomain(u dao.User) domain.User {
	return domain.User{
		Id:          u.Id,
		Email:       u.Email.String,
		Phone:       u.Phone.String,
		Password:    u.Password,
		UserName:    u.UserName,
		UserProfile: u.UserProfile,
		Birthday:    time.UnixMilli(u.Birthday),
		WechatInfo: domain.WechatInfo{
			OpenId:  u.WechatOpenId.String,
			UnionId: u.WechatUnionId.String,
		},
	}
}
func (repo *CacheUserRepository) toEntity(u domain.User) dao.User {
	return dao.User{
		Id: u.Id,
		Email: sql.NullString{
			String: u.Email,
			Valid:  u.Email != "",
		},
		Phone: sql.NullString{
			String: u.Phone,
			Valid:  u.Phone != "",
		},
		Password:    u.Password,
		UserName:    u.UserName,
		UserProfile: u.UserProfile,
		Birthday:    u.Birthday.UnixMilli(),
		WechatOpenId: sql.NullString{
			String: u.WechatInfo.OpenId,
			Valid:  u.WechatInfo.OpenId != "",
		},
		WechatUnionId: sql.NullString{
			String: u.WechatInfo.UnionId,
			Valid:  u.WechatInfo.UnionId != "",
		},
	}
}
func (repo *CacheUserRepository) FindByPhone(ctx context.Context, phone string) (domain.User, error) {
	u, err := repo.dao.FindByPhone(ctx, phone)
	if err != nil {
		return domain.User{}, err
	}
	return repo.toDomain(u), nil
}
func (repo *CacheUserRepository) FindByWechat(ctx context.Context, openId string) (domain.User, error) {
	u, err := repo.dao.FindByWechat(ctx, openId)
	if err != nil {
		return domain.User{}, err
	}
	return repo.toDomain(u), nil
}
